/*
 * Copyright (c) 2005 - 2010 CAS Dev Team
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 4. Neither the name of the CAS Dev. Team nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *      ASMemcachedConnector.cpp
 *
 * $CAS$
 */

#include <ASMemcachedConnector.hpp>

#include <ASDebugHelper.hpp>
#include <ASPool.hpp>
#include <ASLogCodes.hpp>
#include <ASLogger.hpp>

#include <ASMemcachedObject.hpp>

using namespace CAS;

#define C_GLOBAL_RESOURCE      0x00000001
#define C_SERVER_RESOURCE      0x00000002

#define C_CHECK_CONNECTION     0x00000001
#define C_NO_CHECK_CONNECTION  0x00000002

namespace CAS_MOD
{

//
// A constructor
//
ASMemcachedConnector::ASMemcachedConnector()
{
	;;
}

//
// Initialize module
//
INT_32 ASMemcachedConnector::InitModule(CTPP::CDT      & oConfiguration,
                                        CAS::ASPool    & oModulesPool,
                                        CAS::ASPool    & oObjectPool,
                                        CAS::ASLogger  & oLogger)
{
	return 0;
}

//
// Initialize server hook
//
INT_32 ASMemcachedConnector::InitServer(CTPP::CDT      & oConfiguration,
                                        CAS::ASPool    & oGlobalPool,
                                        CAS::ASPool    & oServerPool,
                                        CAS::ASLogger  & oLogger)
{
	using namespace CTPP;
	DEBUG_HELPER(&oLogger, "ASMemcachedConnector::InitServer");

	CDT oTMP = oConfiguration.GetCDT("Memcached");
	CDT::eValType eType = oTMP.GetType();

	if (eType != CDT::HASH_VAL &&
	    eType != CDT::ARRAY_VAL)
	{
		DEBUG_HELPER_MESSAGE("Cannot find any \"Memcached\" configuration section");

		oLogger.Alert("Cannot find any \"Memcached\" configuration section");
		return -1;
	}

	oTMP = oTMP.GetCDT("Connection");
	eType = oTMP.GetType();

	// Only one connection
	if (eType == CDT::HASH_VAL)
	{
		// Create and register object
		return CreateAndRegisterObject(oTMP, oGlobalPool, oServerPool, oLogger);
	}
	else if (eType == CDT::ARRAY_VAL)
	{
		for(UINT_32 iI = 0; iI < oTMP.Size(); ++iI)
		{
			if (CreateAndRegisterObject(oTMP[iI], oGlobalPool, oServerPool, oLogger) == -1) { return -1; }
		}
	}
	else
	{
		STLW::string sError("Invalid type of \"Memcached\" configuration section: \"");
		sError += oTMP.PrintableType(); sError += "\"";
		DEBUG_HELPER_MESSAGE(sError);

		oLogger.Alert(sError.c_str());
		return -1;
	}

	DEBUG_HELPER_MESSAGE("All done with memcached");

return 0;
}

//
// Get object type
//
CCHAR_P ASMemcachedConnector::GetModuleType() const { return "ASMemcachedConnector"; }

//
// Get module name
//
CCHAR_P ASMemcachedConnector::GetObjectName() const { return "ASMemcachedConnector"; }

//
// Create and register new SQL connection
//
INT_32 ASMemcachedConnector::CreateAndRegisterObject(CTPP::CDT      & oMemcConn,
                                                     CAS::ASPool    & oGlobalPool,
                                                     CAS::ASPool    & oServerPool,
                                                     CAS::ASLogger  & oLogger)
{
	using namespace CTPP;
	DEBUG_HELPER(&oLogger, "ASMemcachedConnector::CreateAndRegisterObject");

	ASMemcachedObject * pMemcachedObject = NULL;
	STLW::string sName;

	try
	{
		// Connection name
		sName = oMemcConn.GetCDT("Name").GetString();

		// Serializer
		STLW::string sSerializer = oMemcConn.GetCDT("Serializer").GetString();
		// Get Serialization method
		ASMemcachedObject::eSerializer oSerializer = ASMemcachedObject::JSON;
		if (sSerializer.size())
		{
			if      (strcasecmp("JSON",             sSerializer.c_str()) == 0) { oSerializer = ASMemcachedObject::JSON;            }
			else if (strcasecmp("PERL::STORABLE",   sSerializer.c_str()) == 0) { oSerializer = ASMemcachedObject::PERL_STORABLE;   }
			else if (strcasecmp("PHP::SERIALIZE",   sSerializer.c_str()) == 0) { oSerializer = ASMemcachedObject::PHP_SERIALIZE;   }
			else if (strcasecmp("PYTHON::MARSHALL", sSerializer.c_str()) == 0) { oSerializer = ASMemcachedObject::PYTHON_MARSHALL; }
			else
			{
				STLW::string sError("Memcached object \"");
				sError += sName + " has invalid serialization method: \"" + sSerializer + "\"";
				DEBUG_HELPER_MESSAGE(sError);

				oLogger.Alert(sError.c_str());
				return -1;
			}
		}

		// Connection type: global or server
		const STLW::string sType = oMemcConn.GetCDT("Type").GetString();
		INT_32 iResourceType = 0;

		if      (strcasecmp("Global", sType.c_str()) == 0) { iResourceType = C_GLOBAL_RESOURCE; }
		else if (strcasecmp("Server", sType.c_str()) == 0) { iResourceType = C_SERVER_RESOURCE; }
		else
		{
			oLogger.Alert("Invalid pool type `%s` for memcache connector `%s`", sType.c_str(), sName.c_str());
			return -1;
		}

		// Check existence of another connection
		STLW::string sTMP       = oMemcConn.GetCDT("CheckConnection").GetString();
		INT_32 iCheckExistedConnection;
		if (strcasecmp("yes", sTMP.c_str()) == 0) { iCheckExistedConnection = C_CHECK_CONNECTION;    }
		else                                      { iCheckExistedConnection = C_NO_CHECK_CONNECTION; }

		// I/O Timeout
		UINT_32 iDefaultIOTimeout    = oMemcConn.GetCDT("IOTimeout").GetInt();
		if (iDefaultIOTimeout == 0) { iDefaultIOTimeout = C_DEFAULT_IO_TIMEOUT; }

		// Connection Timeout
		UINT_32 iDefaultConnTimeout  = oMemcConn.GetCDT("ConnTimeout").GetInt();
		if (iDefaultConnTimeout == 0) { iDefaultConnTimeout = C_DEFAULT_CONN_TIMEOUT; }

		// Error timeout
		UINT_32 iErrorTimeout        = oMemcConn.GetCDT("ErrorTimeout").GetInt();
		if (iErrorTimeout == 0) { iErrorTimeout = C_DEFAULT_ERROR_TIMEOUT; }

		// Number of continuum elements
		UINT_32 iMaxContinuumElements = oMemcConn.GetCDT("ContinuumElements").GetInt();
		if (iMaxContinuumElements == 0) { iMaxContinuumElements = C_DEFAULT_CONTINUUM_ELTS; }

		// Compression threshold
		INT_32 iCompressThreshold = oMemcConn.GetCDT("CompressThreshold").GetInt();
		if (iCompressThreshold < 0) { iCompressThreshold = 0; }

		// Compression ratio
		W_FLOAT dCompressRatio = oMemcConn.GetCDT("CompressRatio").GetFloat();
		if (dCompressRatio < 0.0) { dCompressRatio = 0.0; }

		// Create object
		pMemcachedObject = new ASMemcachedObject(oSerializer,
		                                         iErrorTimeout,
		                                         iMaxContinuumElements,
		                                         iCompressThreshold,
		                                         dCompressRatio);

		// Get list of memcached servers
		const CDT oHost = oMemcConn.GetCDT("Host");
		if (oHost.GetType() == CDT::HASH_VAL)
		{
			const STLW::string sHost   = oHost.GetCDT("Name").GetString();
			const UINT_32 iPort        = oHost.GetCDT("Port").GetInt();
			const UINT_32 iConnTimeout = oHost.GetCDT("ConnTimeout").GetInt();
			const UINT_32 iIOTimeout   = oHost.GetCDT("IOTimeout").GetInt();
			const W_FLOAT dWeight      = oHost.GetCDT("Weight").GetFloat();

			INT_32 iId = pMemcachedObject -> AddServer(sHost,
			                                           iPort,
			                                           0,
			                                           dWeight > 0 ? dWeight : 1.0,
			                                           iConnTimeout ? iConnTimeout : iDefaultConnTimeout,
			                                           iIOTimeout ? iIOTimeout : iDefaultIOTimeout);
			oLogger.Debug("AddServer: %d", iId);
		}
		else if (oHost.GetType() == CDT::ARRAY_VAL)
		{
			for (UINT_32 iPos = 0; iPos < oHost.Size(); ++iPos)
			{
				const CDT oHostRec = oHost.GetCDT(iPos);
				if (oHostRec.GetType() != CDT::HASH_VAL)
				{
					oLogger.Alert("Invalid type (`%s`) of record `<Host .... />` for memcache connector `%s`", oHost.PrintableType(), sName.c_str());
					return -1;
				}

				const STLW::string sHost   = oHostRec.GetCDT("Name").GetString();
				const UINT_32 iPort        = oHostRec.GetCDT("Port").GetInt();
				const UINT_32 iConnTimeout = oHostRec.GetCDT("ConnTimeout").GetInt();
				const UINT_32 iIOTimeout   = oHostRec.GetCDT("IOTimeout").GetInt();
				const W_FLOAT dWeight      = oHostRec.GetCDT("Weight").GetFloat();

				INT_32 iId = pMemcachedObject -> AddServer(sHost,
				                                           iPort,
				                                           0,
				                                           dWeight > 0 ? dWeight : 1.0,
				                                           iConnTimeout ? iConnTimeout : iDefaultConnTimeout,
				                                           iIOTimeout ? iIOTimeout : iDefaultIOTimeout);
				oLogger.Debug("AddServer: %d", iId);
			}
		}
		else if (oHost.GetType() == CDT::UNDEF)
		{
			oLogger.Alert("Need at least one record `<Host .... />` for memcache connector `%s`", sName.c_str());
			return -1;
		}
		else
		{
			oLogger.Alert("Invalid type (`%s`) of record `<Host .... />` for memcache connector `%s`", oHost.PrintableType(), sName.c_str());
			return -1;
		}

		if (iResourceType == C_GLOBAL_RESOURCE)
		{
			// Check object
			if (oGlobalPool.CheckResourceByName<ASObject>(sName) == 0)
			{
				STLW::string sError("Memcached object \"");
				sError += sName + " already present in GLOBAL pool";
				DEBUG_HELPER_MESSAGE(sError);

				// Fatal Error
				if (iCheckExistedConnection == C_CHECK_CONNECTION)
				{
					oLogger.Alert(sError.c_str());
					return -1;
				}

				oLogger.Warn(sError.c_str());
				// Only print warning
				return 0;
			}

			// Ok, add connection
			oGlobalPool.RegisterResource<ASObject>(pMemcachedObject, sName);
			// Remember to clear
			vObjects.push_back(pMemcachedObject);

			DEBUG_HELPER_MESSAGE(STLW::string("Memcache connector \"") + sName + "\" registered in GLOBAL pool");
		}
		// C_SERVER_RESOURCE
		else
		{
			// Check object
			if (oServerPool.CheckResourceByName<ASObject>(sName) == 0)
			{
				STLW::string sError("Memcached object \"");
				sError += sName + " already present in SERVER pool";
				DEBUG_HELPER_MESSAGE(sError);

				// Fatal Error
				if (iCheckExistedConnection == C_CHECK_CONNECTION)
				{
					oLogger.Alert(sError.c_str());
					return -1;
				}

				oLogger.Warn(sError.c_str());
				// Only print warning
				return 0;
			}

			// Ok, add connection
			oServerPool.RegisterResource<ASObject>(pMemcachedObject, sName);
			// Remember to clear
			vObjects.push_back(pMemcachedObject);

 			DEBUG_HELPER_MESSAGE(STLW::string("Memcache connector \"") + sName + "\" registered in SERVER pool");
		}
	}
	catch(STLW::exception &e)
	{
		if (pMemcachedObject != NULL) { delete pMemcachedObject; }
		STLW::string sError("Cannot create & register \"");
		sError += sName + "\" connection object in pool: " + e.what();

		oLogger.Alert(sError.c_str());
		return -1;
	}

return 0;
}

//
// A destructor
//
ASMemcachedConnector::~ASMemcachedConnector() throw()
{
	STLW::vector<ASObject *>::iterator itvObjects = vObjects.begin();
	while (itvObjects != vObjects.end())
	{
		delete *itvObjects;
		++itvObjects;
	}
}

} // namespace CAS_MOD
// End.

